package top.infopub.httpclient.cmpt;


import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import top.infopub.httpclient.base.HttpEnum;
import top.infopub.util.IdFactory;


/**
 * Https请求处理类
 * @author Awoke
 * @version 2018年4月27日
 * @see HttpsAction
 * @since
 */
public final class HttpsAction {

    private final Logger logger = LoggerFactory.getLogger(HttpsAction.class);

    private final Map<String, CloseableHttpClient> httpClients = new HashMap<String, CloseableHttpClient>();

    // 生成http连接
    private ElideHttpClientGenerator httpClientGenerator = new ElideHttpClientGenerator();

    // instance
    private static HttpsAction instance = new HttpsAction();

    public static HttpsAction getInstance() {
        return instance;
    }

    /**
     * 执行请求
     * @param request
     * @param site
     * @return 
     * @see
     */
    public Response doRequest(Request request, Site site) {
        Map<String, String> headers = request.getHeaders();
        logger.info(request.toString());
        CloseableHttpResponse httpResponse = null;
        int statusCode = 0;
        try {
            HttpUriRequest httpUriRequest = getHttpUriRequest(request, site, headers);
            httpResponse = getHttpClient(site).execute(httpUriRequest);
            statusCode = httpResponse.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                // 封装返回的response 
                Response page = handleResponse(request, request.getEntityCharset(), httpResponse);
                return page;
            }
            else {
                String _content = getContent(httpResponse, request.getEntityCharset());
                logger.warn("code error " + statusCode + "\t" + request.getUrl() + ",error:"
                            + _content);
                Response response = new Response();
                response.setStatusCode(statusCode);
                if (statusCode == 302) {
                    Header _header = httpResponse.getFirstHeader("Location");
                    logger.warn("code error " + statusCode + "\t location " + _header.getValue());
                    response.setExtendsions(_header.getValue());
                    response.setHttpResponse(httpResponse);
                }
                else if (statusCode == 500) {
                    response.setExtendsions(_content);
                }
                return response;
            }
        }
        catch (Exception e) {
            logger.warn("doRequest page " + request.getUrl() + " error", e);
            Response response = new Response();
            response.setStatusCode(statusCode);
            response.setExtendsions("exception");
            return response;
        }
        finally {
            try {
                if (httpResponse != null) {
                    // ensure the connection is released back to pool
                    EntityUtils.consume(httpResponse.getEntity());
                }
            }
            catch (IOException e) {
                logger.warn("close response fail", e);
            }
        }
    }

    public Response doDownLoadFile(Request request, Site site) {

        Map<String, String> headers = request.getHeaders();

        logger.info(request.toString());
        CloseableHttpResponse httpResponse = null;
        int statusCode = 0;
        try {
            HttpUriRequest httpUriRequest = getHttpUriRequest(request, site, headers);
            httpResponse = getHttpClient(site).execute(httpUriRequest);
            statusCode = httpResponse.getStatusLine().getStatusCode();
            HttpEntity entity = httpResponse.getEntity();
            if (statusCode >= 300) {
                EntityUtils.consume(entity);
                logger.warn("code error " + statusCode + "\t" + request.getUrl());
                throw new RuntimeException("statusCode is " + statusCode);
            }
            else {
                Response page = new Response();
                page.setUrl(request.getUrl());
                page.setRequest(request);
                page.setStatusCode(httpResponse.getStatusLine().getStatusCode());
                page.setHttpResponse(httpResponse);
                String fileName = null;
                Header[] dispositionHeaders = httpResponse.getHeaders("Content-disposition");
                if (dispositionHeaders != null && dispositionHeaders.length > 0) {
                    fileName = extractFileName(dispositionHeaders[0].getValue());
                }
                if (fileName == null || "".equals(fileName.trim())) {
                    fileName = getCustomerFileName(httpResponse);
                }
                InputStream inputStream = entity.getContent();
                File tempFile = new File(FileUtils.getTempDirectory(), fileName);
                logger.info("get file,{}", tempFile.getAbsolutePath());
                FileUtils.copyInputStreamToFile(inputStream, tempFile);
                page.setFile(tempFile);
                return page;
            }
        }
        catch (IOException e) {
            logger.warn("doRequest page " + request.getUrl() + " error", e);
            return null;
        }
        finally {
            try {
                if (httpResponse != null) {
                    // ensure the connection is released back to pool
                    EntityUtils.consume(httpResponse.getEntity());
                }
            }
            catch (IOException e) {
                logger.warn("close response fail", e);
            }
        }
    }

    public void cleanUp(Site site) {
        if (site == null) {
            return;
        }
        String domain = site.getDomain();
        CloseableHttpClient httpClient = httpClients.get(domain);
        if (httpClient != null) {
            synchronized (this) {
                httpClient = httpClients.get(domain);
                try {
                    httpClient.close();
                }
                catch (IOException e) {
                    logger.error("关闭domain失败", e);
                }
                httpClients.remove(domain);
            }
        }
    }

    private CloseableHttpClient getHttpClient(Site site) {
        if (site == null) {
            throw new RuntimeException("site is null");
        }
        String domain = site.getDomain();
        CloseableHttpClient httpClient = httpClients.get(domain);
        if (httpClient == null) {
            synchronized (this) {
                httpClient = httpClients.get(domain);
                if (httpClient == null) {
                    httpClient = httpClientGenerator.getClient(site);
                    httpClients.put(domain, httpClient);
                }
            }
        }
        return httpClient;
    }

    private String getCustomerFileName(CloseableHttpResponse httpResponse) {
        String prefix = IdFactory.getUUIDSerialNumber();
        Header ctHeader = httpResponse.getLastHeader("Content-Type");
        if (ctHeader != null) {
            String _v = ctHeader.getValue();
            if (_v != null) {
                _v = _v.trim();
            }
            if (_v != null && _v.contains(";")) {
                _v = _v.substring(0, _v.indexOf(";"));
            }
            return prefix + ContentType.getSuffix(_v);
        }
        return prefix;
    }

    private String extractFileName(String headerValue) {
        String fileName = null;
        Pattern regex = Pattern.compile("(?<=filename=\").*?(?=\")");
        Matcher regexMatcher = regex.matcher(headerValue);
        if (regexMatcher.find()) {
            fileName = regexMatcher.group();
        }
        return fileName;
    }

    protected boolean statusAccept(Set<Integer> acceptStatCode, int statusCode) {
        return acceptStatCode.contains(statusCode);
    }

    protected HttpUriRequest getHttpUriRequest(Request request, Site site,
                                               Map<String, String> headers) {
        RequestBuilder requestBuilder = selectRequestMethod(request).setUri(request.getUrl());
        if (headers != null) {
            for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
                requestBuilder.addHeader(headerEntry.getKey(), headerEntry.getValue());
            }
        }
        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom().setConnectionRequestTimeout(
            request.getTimeout()).setSocketTimeout(request.getTimeout()).setConnectTimeout(
            request.getTimeout()).setRedirectsEnabled(false);
        requestConfigBuilder.setCookieSpec(CookieSpecs.IGNORE_COOKIES);
        requestBuilder.setConfig(requestConfigBuilder.build());

        return requestBuilder.build();
    }

    protected RequestBuilder selectRequestMethod(Request request) {
        String method = request.getMethod();
        if (method == null || method.equalsIgnoreCase(HttpEnum.Method.GET.getMethod())) {
            // default get 默认GET方式请求
            return RequestBuilder.get();
        }
        else if (method.equalsIgnoreCase(HttpEnum.Method.POST.getMethod())) {
            RequestBuilder requestBuilder = RequestBuilder.post();
            requestBuilder.setEntity(request.getEntity());
            return requestBuilder;
        }
        else if (method.equalsIgnoreCase(HttpEnum.Method.HEAD.getMethod())) {
            return RequestBuilder.head();
        }
        else if (method.equalsIgnoreCase(HttpEnum.Method.PUT.getMethod())) {
            return RequestBuilder.put();
        }
        else if (method.equalsIgnoreCase(HttpEnum.Method.DELETE.getMethod())) {
            return RequestBuilder.delete();
        }
        else if (method.equalsIgnoreCase(HttpEnum.Method.TRACE.getMethod())) {
            return RequestBuilder.trace();
        }
        throw new IllegalArgumentException("Illegal HTTP Method " + method);
    }

    protected Response handleResponse(Request request, String charset, HttpResponse httpResponse)
        throws IOException {
        Response page = new Response();
        if (request.isFile()) {
            page.setFile(getFileContent(httpResponse));
        }
        else {
            String content = getContent(httpResponse, charset);
            page.setRawText(content);
        }
        page.setUrl(request.getUrl());
        page.setRequest(request);
        page.setStatusCode(httpResponse.getStatusLine().getStatusCode());
        page.setHttpResponse(httpResponse);
        return page;
    }

    protected String getContent(HttpResponse httpResponse, String charset)
        throws IOException {
        byte[] contentBytes = IOUtils.toByteArray(httpResponse.getEntity().getContent());
        // 返回值 字符集
        return new String(contentBytes, charset);
    }

    protected File getFileContent(HttpResponse httpResponse)
        throws UnsupportedOperationException, IOException {
        InputStream inputStream = httpResponse.getEntity().getContent();
        File tempFile = new File(FileUtils.getTempDirectory(), IdFactory.getUUIDSerialNumber());
        FileUtils.copyInputStreamToFile(inputStream, tempFile);

        return tempFile;
    }

    private HttpsAction() {}

}
